﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.Windows;
using System.Diagnostics;

namespace Common.Model
{
    public class CSpielfeld : CBase
    {
        public enum FieldColor { NotSetNotPossible = 0, Possible = 10, White = -1, Black = 1 }//0 ist der default wert
        public enum GameMode { Default = 0, Reversi = 1 }//0 ist der default wert

        private FieldColor[,] _Feld;

        #region getter / setter

        public FieldColor[,] Feld
        {
            get
            {
                return _Feld;
            }
            set
            {
                _Feld = value;
                reSetSize();
            }
        }
        public FieldColor this[CPoint p]
        {
            get
            {
                return Feld[p.Y, p.X];
            }
            set
            {
                Feld[p.Y, p.X] = value;
            }
        }

        /// <summary>
        /// The mode of the field, for setting the mode use reInit
        /// </summary>
        public GameMode Mode { get; set; }

        /// <summary>
        /// String, welcher das Feld beschreibt
        /// </summary>
        public string FeldString
        {
            get
            {
                int oldY = 0;
                string spielfeldStr = "";
                AllPoints.ForEach((currentPoint) =>
                {
                    if (oldY != currentPoint.Y)
                    {
                        oldY = currentPoint.Y;
                        spielfeldStr += Environment.NewLine;
                    }

                    switch (this[currentPoint])
                    {
                        case FieldColor.Possible:
                            spielfeldStr += "p";
                            break;
                        case FieldColor.White:
                            spielfeldStr += "o";
                            break;
                        case FieldColor.Black:
                            spielfeldStr += "x";
                            break;
                        default:
                            spielfeldStr += " ";
                            break;
                    }

                    spielfeldStr += "|";
                });
                return spielfeldStr;
            }
        }

        private List<CPoint> _AllPoints;
        /// <summary>
        /// alle punkte, welche im feld liegen.
        /// </summary>
        public List<CPoint> AllPoints
        {
            get
            {
                if (_AllPoints == null)
                {
                    _AllPoints = new List<CPoint>();
                    for (int y = 0; y < this.Size; y++)
                    {
                        for (int x = 0; x < this.Size; x++)
                        {
                            _AllPoints.Add(new CPoint(x, y));
                        }
                    }
                }
                return _AllPoints;
            }
        }

        /// <summary>
        /// grösse des spielfelds
        /// </summary>
        public int Size
        {
            get
            {
                return (int)Math.Sqrt(Feld.Length);
            }
        }

        private static CSpielfeld _Instance;
        public static CSpielfeld Instance
        {
            get
            {
                if (_Instance == null)
                    _Instance = new CSpielfeld();
                return _Instance;
            }
            set
            {
                _Instance = value;
            }
        }
        #endregion

        #region konstuktor

        private CSpielfeld(GameMode mode = GameMode.Default, int size = 8)
        {
            reInit(mode, size);
        }
        public CSpielfeld(CSpielfeld oldFeld)
        {
            this.Mode = oldFeld.Mode;
            this.Feld = new FieldColor[oldFeld.Size, oldFeld.Size];
            oldFeld.AllPoints.ForEach((currentPoint) => this[currentPoint] = oldFeld[currentPoint]);
        }

        #endregion

        /// <summary>
        /// initialisiert das spielfeld neu
        /// </summary>
        /// <param name="mode"></param>
        public void reInit(GameMode mode = GameMode.Default, int size = 8)
        {
            Feld = new FieldColor[size, size];

            AllPoints.ForEach((currentPoint) => this[currentPoint] = FieldColor.NotSetNotPossible);

            Feld[size / 2 - 1, size / 2 - 1] = FieldColor.White;
            Feld[size / 2, size / 2 - 1] = mode == GameMode.Default ? FieldColor.Black : FieldColor.White;

            Feld[size / 2 - 1, size / 2] = FieldColor.Black;
            Feld[size / 2, size / 2] = mode == GameMode.Default ? FieldColor.White : FieldColor.Black;

        }

        /// <summary>
        /// muss aufgerufen werden, wenn das spielfeld neu gsetzt wird
        /// </summary>
        private void reSetSize()
        {
            _AllPoints = null;
            CKISpieler.reSetFeldgewichtung();
        }

        /// <summary>
        /// setzt alle felder von possible auf notset
        /// </summary>
        private void resetNotSettedFields()
        {
            foreach (CPoint currentPoint in AllPoints.Where(p => this[p] == FieldColor.Possible))
            { 
                this[currentPoint] = FieldColor.NotSetNotPossible;
            }
        }

        /// <summary>
        /// geht vom startpunkt in die angegebene richtung und giebt zurück ob ein mögliches feld für den nächsten zug gefunden wurde und giebt den finalen punkt (falls vorhanden) zurück
        /// </summary>
        /// <param name="zurZeitAmZug"></param>
        /// <param name="direction"></param>
        /// <param name="startPoint"></param>
        /// <param name="finalPoint"></param>
        /// <param name="searchForFieldColor">falls nicht nach einem leeren möglichen feld gesucht werden soll, der typ nach welchem gesucht werden soll, wird eigentlich nur gebraucht um bei einem zug die felder zu drehen, dh. die alten punkte, zwischen welchen gedreht werden muss zu finden</param>
        /// <returns></returns>
        private bool getPossibleField(FieldColor zurZeitAmZug, CPoint.Direction direction, CPoint startPoint, out CPoint finalPoint, FieldColor searchForFieldColor = FieldColor.NotSetNotPossible)
        {
            finalPoint = new CPoint();

            bool foundOne = false;
            startPoint = startPoint.getNextPointInDirection(direction);

            while (startPoint.X < this.Size && startPoint.Y < this.Size && startPoint.Y >= 0 && startPoint.X >= 0)
            {
                if (this[startPoint] == searchForFieldColor && foundOne)
                {
                    finalPoint = new CPoint(startPoint.X, startPoint.Y);
                    return true;
                }
                if (this[startPoint] == (zurZeitAmZug == FieldColor.Black ? FieldColor.White : FieldColor.Black))
                {
                    foundOne = true;
                }
                if (this[startPoint] == zurZeitAmZug || this[startPoint] == FieldColor.NotSetNotPossible || this[startPoint] == FieldColor.Possible)
                {
                    return false;
                }

                startPoint = startPoint.getNextPointInDirection(direction);
            }

            return false;
        }

        /// <summary>
        /// setzt die möglichen felder neu und giebt die anzahl der möglichen felder zurück
        /// </summary>
        /// <param name="zurZeitAmZug">spieler, welcher als nächster am zug ist</param>
        /// <returns></returns>
        public List<CPoint> reCalculatePossibleFields(CSpieler zurZeitAmZug)
        {
            return reCalculatePossibleFields(zurZeitAmZug.Farbe);
        }
        /// <summary>
        ///  setzt die möglichen felder neu und giebt die anzahl der möglichen felder zurück
        /// </summary>
        /// <param name="currentPlayers">die farbe des aktuellen spielers</param>
        /// <returns></returns>
        public List<CPoint> reCalculatePossibleFields(FieldColor currentPlayers)
        {
            List<CPoint> possiblePoints = new List<CPoint>();
            resetNotSettedFields();
            AllPoints.ForEach((currentPoint) =>
            {
                if (this[currentPoint] == currentPlayers)
                {
                    foreach (var d in Enum.GetValues(typeof(CPoint.Direction)))
                    {
                        CPoint finalPoint;
                        if (getPossibleField(currentPlayers, (CPoint.Direction)d, currentPoint, out finalPoint))
                        {
                            this[finalPoint] = FieldColor.Possible;
                            possiblePoints.Add(finalPoint);
                        }
                    }
                }
            });
            return possiblePoints;
        }

        /// <summary>
        /// vollzieht einen zug und giebt zurück ob dieser vollendet werden konnte
        /// </summary>
        /// <param name="p"></param>
        /// <param name="zurZeitAmZug"></param>
        /// <param name="gedreht">gibt an welche felder gedreht wurden</param>
        /// <returns></returns>
        public bool turn(CPoint point, CSpieler spielerAmZug, out List<CPoint> gedreht)
        {
            return turn(point, spielerAmZug.Farbe, out gedreht);
        }
        /// <summary>
        /// vollzieht einen zug und giebt zurück ob dieser vollendet werden konnte
        /// </summary>
        /// <param name="point"></param>
        /// <param name="spielerAmZug"></param>
        /// <param name="gedreht">gibt an welche felder gedreht wurden</param>
        /// <returns></returns>
        public bool turn(CPoint point, FieldColor spielerAmZug, out List<CPoint> gedreht)
        {
            gedreht = new List<CPoint>();
            try
            {
                if (this[point] == FieldColor.Possible)
                {
                    this[point] = spielerAmZug;
                    gedreht.Add(point);
                    foreach (var d in Enum.GetValues(typeof(CPoint.Direction)))
                    {
                        CPoint finalPoint;
                        CPoint.Direction dire = (CPoint.Direction)d;
                        if (getPossibleField(spielerAmZug, dire, point, out finalPoint, spielerAmZug))
                        {
                            while ((finalPoint = finalPoint.getNextPointInOppositeDirection(dire)) != point)
                            {
                                if (!gedreht.Contains(finalPoint))
                                    gedreht.Add(finalPoint);
                            }
                        }
                    }

                    gedreht.ForEach((p) => this[p] = spielerAmZug);
                    return true;
                }
                else
                {
                    return false;
                }
            }
            catch (Exception e)
            {
#if DEBUG
                Debug.Print(e.StackTrace);
#endif
                return false;
            }
        }
        /// <summary>
        /// vollzieht einen zug und giebt zurück ob dieser vollendet werden konnte
        /// </summary>
        /// <param name="p"></param>
        /// <param name="zurZeitAmZug"></param>
        /// <returns></returns>
        public bool turn(CPoint point, CSpieler spielerAmZug)
        {
            List<CPoint> s;
            return turn(point, spielerAmZug, out s);
        }

        public override string ToString()
        {
            try
            {
                return "CSpielfeld=[Modus=" + this.Mode.ToString() + ", Feld=" + FeldString + "]";
            }
            catch (Exception)
            {
                return base.ToString();
            }
        }
    }
}
